#include <ctype.h>
#include <string.h>
#include <kernel.h>
#include <vga.h>
#include <ia8259a.h>

#define IDT_BASE                (0x00100000)
#define IDT_SIZE                (0x07FF)

#define INT_ATTR_VAILID         (0x8000)
#define INT_ATTR_INTTYPE        (0x0E00)
#define INT_ATTR_TRAPTYPE       (0x0F00)

#define ASM_INT_CALLADDR_MAGIC  0xF1F1F1F1
#define ASM_INT_PARAM_MAGIC     0x1F1F1F1F

#define ASM_INT_PUSHF           0x9C
#define ASM_INT_IRET            0xCF

#define INT_CALL_PTR_BASE       0x00100800
#define INT_KERNEL_ROUTINE_BASE 0x00101000

int int_base_num = IA_80x86_MI_BASE_NUM;

EOI_FUNC eoi_ptr;

void loadidt(int * base_addr, int * limit)
{
    int b[2] = {0};

    asm __volatile__
        (
        "leal %0, %%ebx\n\t"
        "sidt %%ds:(%%ebx)\n\t"
        :"=o"(b)
        ::"%ebx"
        );

    *limit = b[0] & 0xFFFF;

    *base_addr = *(int *)(((char *)b) + 2);
}

void setidt(int base_addr, int limit)
{
    unsigned int b[2] = {0};

    if (limit > 0x7FF)
        return;

    b[0] = (limit & 0xFFFF) << 16;
    b[1] = base_addr;

    asm __volatile__
        (
        "leal %0, %%ebx\n\t"
        "addl $2, %%ebx\n\t"
        "lidt %%ds:(%%ebx)\n\t"
        :"=o"(b)
        ::"%ebx"
        );
}

void install_vitual_int(char int_num, int ip)
{
    write_to_real(&ip, sizeof(int), int_num * 4 + INT_CALL_PTR_BASE);
}

int installInterrupt(char int_num, int offset, short segindex, int paramcount)
{
    IntDescriptor id;
    int realptr;

    int idt_base;
    int idt_limit;

    loadidt(&idt_base, &idt_limit);

    memset(&id, 0, sizeof(id));

    realptr = int_num * sizeof(id) + idt_base;

    if (realptr > idt_base + idt_limit)
        return -1;

    id.Segment = segindex;
    id.OffsetL = offset & 0xFFFF;
    id.OffsetH = (offset >> 16);
    id.IntAttr = INT_ATTR_VAILID | INT_ATTR_INTTYPE | (paramcount & 0xF);

    write_to_real(&id, sizeof(id), realptr);

    return 0;
}

void init_idt()
{
    setidt(IDT_BASE, IDT_SIZE);
}

static void empty_int_routine(int int_num)
{
    char c;

    c = vga_in_mem_ch(int_num);

    if (c >= '0' && c < '9')
        c++;
    else
        c = '0';

    vga_out_mem_ch(int_num, c);

    if (int_num >= int_base_num)
        {
        eoi_8259a();
        }
}

static void call_int_routine()
{
    int_lock();

    asm __volatile__
        (
        "pusha\n\t"
        "movw %0, %%ax\n\t"
        "movw %%ax, %%ds\n\t"
        "movl %1, %%eax\n\t"
        "push %%eax\n\t"
        "movl %2, %%eax\n\t"
        "call *%%ds:(%%eax)\n\t"
        "pop %%eax\n\t"
        "popa\n\t"
        ::
        "i"(SEG_GLOBAL_FLAT_SELECTOR),
        "i"(ASM_INT_PARAM_MAGIC),
        "i"(ASM_INT_CALLADDR_MAGIC):"%eax"
        );

    int_unlock();

    asm __volatile__
        (
        "iret\n\t"
        );
}

void init_int()
{
    char codebuf[256];
    int cl, pl;
    int i, j, l;
    int cs, ip;
    char * p = (char *)call_int_routine;

    init_idt();

    cs = GetCurrentCodeSeg();
    ip = (int)empty_int_routine;

    for (i = 0; i < 256; i++)
        {
        if ((unsigned char)p[i] == ASM_INT_PUSHF)
            break;
        }

    for (l = 0; l < 256; l++, i++)
        {
        codebuf[l] = p[i];

        if (*(int *)(p + i) == ASM_INT_CALLADDR_MAGIC)
            cl = l;

        if (*(int *)(p + i) == ASM_INT_PARAM_MAGIC)
            pl = l;

        if ((unsigned char)codebuf[l] == ASM_INT_IRET)
            break;
        }

    for (j = 0; j < 256; j++)
        {
        *(int *)(codebuf + pl) = j;

        *(int *)(codebuf + cl) = j * 4 + INT_CALL_PTR_BASE;

        write_to_real(codebuf, i + 1, INT_KERNEL_ROUTINE_BASE + j * 256);

        installInterrupt(j, INT_KERNEL_ROUTINE_BASE + j * 256, cs, 0);

        install_vitual_int(j, ip);
        }

    init_8259a(int_base_num);

    eoi_ptr = eoi_8259a;

    int_reset();
}

